import pandas as pd
import numpy as np
import pymongo
import datetime
import math
#import statsmodels.api as sm #统计运算
import scipy.stats as scs #科学计算
import matplotlib.pyplot as plt #绘图
import scipy.optimize as sco

#x=Symbol('x')
#print(solve(x * 2 - 6, x))


# Asect1 = 6.12% sd = 1.72%

def GenerateTestData(numAssets, numObs):
    # return_vec = np.random.randn(n_assets, n_obs)

    dates = pd.date_range('20130101', periods=numObs)
    np.random.seed(1)
    data = pd.DataFrame(np.random.randn(numObs, numAssets), index=dates, columns=list('ABCD'))
    # print(data)

    returns = data / 100.0
    #returns = np.log(data / data.shift(1))
    print(returns)
    #
    return returns


def Load_Data(full_path_filename):
    df_price = pd.read_csv(full_path_filename)
    df_price.drop(['DateTime'], axis=1, inplace=True)
    df_returns = np.log(df_price / df_price.shift(1))
    print(df_returns)
    return df_returns


def Random_Weights(count):
    weights = np.random.random(count)
    weights /= np.sum(weights)
    return weights


def Calc_Portfolio_Profile(weights, annual_returns, annual_covariance_matrix):
    #
    port_return = np.sum(annual_returns * weights)
    port_variance = np.dot(weights.T, np.dot(annual_covariance_matrix, weights))

    #sum = 0
    #for i in range(assetsCount):
    #    for j in range(assetsCount):
    #       sum += weights[i] * weights[j] *  annualCovarianceMatrix[i][j]
    #port_stdDeviation2 = np.sqrt(sum)

    port_std_deviation = np.sqrt(port_variance)
    sharpe = port_return / port_std_deviation

    profile = {"return": port_return, "variance": port_variance, "sharpe": sharpe}

    # 等效
    profile = {}
    profile["return"] = port_return
    profile["variance"] = port_variance
    profile["sharpe"] = sharpe

    #
    return profile


def MinSharpe(weights, annual_returns, annual_covariance_matrix):
    profile = Calc_Portfolio_Profile(weights,annual_returns, annual_covariance_matrix)
    sharpe = profile["sharpe"]
    return -1 * sharpe


def MinVariance(weights,annual_returns, annual_covariance_matrix):
    profile = Calc_Portfolio_Profile(weights,annual_returns, annual_covariance_matrix)
    variance = profile["variance"]
    return variance


def FindMaxSharpePortfolio():
    pass


#---Monte Carlo---
def MonteCarlo(annual_returns, annual_covariance_matrix, iteraton = 1000, plot=True):
    numAssets = len(annual_returns)
    port_returns = []
    port_variances = []
    for p in range(iteraton):
        weights = Random_Weights(numAssets)
        #print(weights)
        profile = Calc_Portfolio_Profile(weights, annual_returns, annual_covariance_matrix)
        #print("Return " + str(values[0]) + " SD " + str(values[1]))
        port_returns.append(profile["return"])
        port_variances.append(profile["variance"])

    port_returns = np.array(port_returns)
    port_variances = np.array(port_variances)

    if plot:
        risk_free = 0.04
        plt.figure(figsize=(8, 4))
        plt.scatter(port_variances, port_returns, c=(port_returns - risk_free) / port_variances, marker='o')
        plt.grid(True)
        plt.xlabel('excepted volatility')
        plt.ylabel('expected return')
        plt.colorbar(label='Sharpe ratio')
        plt.show()

    return port_returns, port_variances


def EfficientFrontier(exp_returns, covariance_matrix):
    #
    num_assets = len(exp_returns)
    #
    maxSharp_weight = MaxSharpe_Optimize(exp_returns, covariance_matrix)
    maxSharp_profile = Calc_Portfolio_Profile(maxSharp_weight, exp_returns, covariance_matrix)
    print(maxSharp_profile)
    #
    minVariance_weight = MinVariance_Optimize(exp_returns, covariance_matrix)
    minVariance_profile = Calc_Portfolio_Profile(minVariance_weight, exp_returns, covariance_matrix)
    print(minVariance_profile)

    # 在不同目标收益率水平（target_returns）循环时，最小化的一个约束条件会变化。
    target_returns = np.linspace(minVariance_profile["return"], maxSharp_profile["return"], 10)
    target_variance = []

    for tar in target_returns:
        bnds = tuple((0, 1) for x in range(num_assets))
        # #1 Constraint 指定目標收益率
        # #2 Constraint 总权重为1
        # print(tar)
        cons = ({'type': 'eq', 'fun': lambda x: Calc_Portfolio_Profile(x, exp_returns, covariance_matrix)["return"] - tar},
                {'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
        res = sco.minimize(MinVariance, num_assets * [1./num_assets, ],
                           args=(exp_returns, covariance_matrix,),
                           method='SLSQP',
                           bounds=bnds, constraints=cons)
        target_variance.append(res['fun'])
        print("Frontier Ret: " + str(tar) + " sd " + str(res['fun']))

    target_variance = np.array(target_variance)

    port_returns, port_variances = MonteCarlo(exp_returns, covariance_matrix, 1000, plot=False)

    plt.figure(figsize=(8, 4))

    # 圆圈：蒙特卡洛随机产生的组合分布
    # risk_free = 0.04
    # plt.scatter(port_variances, port_returns, c=(port_returns - risk_free) / port_variances, marker='o')
    plt.scatter(port_variances, port_returns, c=port_returns / port_variances, marker='o')

    # 叉号：有效前沿
    plt.scatter(target_variance, target_returns, c=target_returns / target_variance, marker='x')

    # 红星：标记最高sharpe组合
    plt.plot(maxSharp_profile["variance"], maxSharp_profile["return"], 'r*', markersize=15.0)

    # 黄星：标记最小方差组合
    plt.plot(minVariance_profile["variance"], minVariance_profile["return"], 'y*', markersize=15.0)

    plt.grid(True)
    plt.xlabel('expected volatility')
    plt.ylabel('expected return')
    plt.colorbar(label='Sharpe ratio')
    plt.show()


# ---Covarince = Rho * SD * SD---
def Calc_Correlation(correlation, annual_std_dev):
    #
    num_assets = len(annual_std_dev)
    #
    for i in range(0, num_assets):
        for j in range(num_assets):
            if j < i:
                correlation[i][j] = correlation[j][i]
            if j == i:
                correlation[i][j] = 1

    # Covarince = Rho * SD * SD
    for i in range(num_assets):
        for j in range(num_assets):
            correlation[i][j] = correlation[i][j] * annual_std_dev[i] * annual_std_dev[j]

    print("Cov")
    df = pd.DataFrame(correlation)
    print(df)
    return df


def MaxSharpe_Optimize(exp_returns, covariance_matrix):
    print(" Sharpe最大 ")
    #
    num_assets = len(exp_returns)
    cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    bnds = tuple((0, 1) for x in range(num_assets))
    init_weight = num_assets * [1. / num_assets]
    # init_weight = num_assets * [0.25, 0.25, 0.25, 0.25]
    #
    opts = sco.minimize(MinSharpe,
                        init_weight,
                        args=(exp_returns, covariance_matrix, ),
                        method='SLSQP',
                        bounds=bnds,
                        constraints=cons)
    print(opts)
    # [ 0.31296679,  0.03839007,  0.64446006,  0.00418308]
    return opts.x


def MinVariance_Optimize(exp_returns, covariance_matrix):
    print(" 方差最小 ")
    #
    num_assets = len(exp_returns)
    cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    bnds = tuple((0, 1) for x in range(num_assets))
    init_weight = num_assets * [1. / num_assets]
    # init_weight = num_assets * [0.25, 0.25, 0.25, 0.25]

    optv = sco.minimize(MinVariance,
                        init_weight,
                        args=(exp_returns, covariance_matrix, ),
                        method='SLSQP',
                        bounds=bnds,
                        constraints=cons)
    print(optv)
    # [ 0.34351965,  0.19567223,  0.25471517,  0.20609294]
    return optv.x


def Test1_Random_Data():

    # ---NUMBER OF ASSETS---
    numAssets = 4

    # ---NUMBER OF OBSERVATIONS---
    numObs = 6

    # ---Geneate Test Data---
    returns = GenerateTestData(numAssets, numObs)
    # returns = LoadData()

    # ---Mean---
    print("Means")
    exp_returns = returns.mean() * 252
    print(exp_returns)

    # ---Sd---
    print("Std Dev")
    print(returns.std() * math.sqrt(252))

    # ---Correlation---
    print("Correlation")
    print(returns.corr())

    # ---Covariance---
    print("Covariance")
    covariance_matrix = returns.cov() * 252
    print(covariance_matrix)

    MaxSharpe_Optimize(exp_returns, covariance_matrix)

    MonteCarlo(exp_returns, covariance_matrix, iteraton=1000)

    EfficientFrontier(exp_returns, covariance_matrix)


def Test2_Specific_Data():

    num_assets = 4

    exp_returns = [0.10, 0.16, 0.04, 0.058]
    exp_std_dev = [0.20, 0.30, 0.02, 0.038]

    correlation = [[1.000000, 0.8404, -0.0256, -0.0065],
                   [0, 1.000000, -0.0377, 0.0020],
                   [0, 0, 1.000000, 0.2268],
                   [0, 0, 0, 1.000000]]

    covariance_matrix = Calc_Correlation(correlation, exp_std_dev)

    MaxSharpe_Optimize(exp_returns, covariance_matrix)
    # MonteCarlo(exp_returns, covariance_matrix, iteraton=1000)
    EfficientFrontier(exp_returns, covariance_matrix)


if __name__ == '__main__':

    Test1_Random_Data()

    # Test2_Specific_Data()



